package com.nariano.server.services.mapservice.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.orm.jpa.support.JpaDaoSupport;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import com.nariano.server.domain.entities.Area;
import com.nariano.server.domain.entities.AreaBoundaries;
import com.nariano.server.domain.entities.City;
import com.nariano.server.domain.entities.Location;
import com.nariano.server.domain.entities.Population;
import com.nariano.server.domain.entities.PopulationType;
import com.nariano.server.domain.entities.ResourceType;
import com.nariano.server.domain.entities.User;
import com.nariano.server.publisher.ClientPublisher;
import com.nariano.server.publisher.events.CityCreated;
import com.nariano.server.publisher.events.PopulationChanged;
import com.nariano.server.services.mapservice.MapService;

@Transactional
public class MapServiceJpa extends JpaDaoSupport implements MapService {
    
    private int size = 0;
    
    @Autowired
	@Qualifier(value="clientPublisher")
    ClientPublisher clientPublisher;
    
    
    public void init() {
        List<Area> areas = getAreas();
		int numberOfAreas = areas.size();
        if (numberOfAreas>0) {
            size = (int) Math.sqrt(numberOfAreas);
        }
    }

    public void createMap(int size) {
        if (getAreas().size()>0) {
            throw new IllegalStateException("Aborted creation of map since one already exists");
        }
        this.size = size;
        JpaTemplate ht = getJpaTemplate();
        for(int x=0;x<size;x++) {
            for(int y=0;y<size;y++) {
                Area a = createArea(x,y);
                ht.persist(a);
            }
        }
    }
    
    public void deleteMap() {
         List<Area> areas = getAreas();
         for(Area a : areas) {
        	 getJpaTemplate().remove(a);
         }
    }
  
    private Area createArea(int x, int y) {
        Area a = new Area(new Location(x,y));
        a.setResourceType(createResources());
        return a;
    }

    /**
     * ONE resource only!!!!
     * @return
     */
    private ResourceType createResources() {
        Random rand = new Random();
        ResourceType type = ResourceType.values()[rand.nextInt(ResourceType.values().length)];
        return type;
    }

    public List<Area> getAreas() {
        return getJpaTemplate().find("select a from com.nariano.server.domain.entities.Area a");
    }

    public List<List<Area>> getAreas(Location center, int width) {
         
        int lowerX = center.getX() - width;
        int lowerY = center.getY() - width;
        int upperX = center.getX() + width;
        int upperY = center.getY() + width;
        
     // reposition positions if out of map
        if (lowerX < 0) {
           // move east
            lowerX = 0;
            upperX = 2*width;
        } else if (upperX >= size) {
           // move west
            lowerX = size-2*width-1;
            upperX = size-1;
        }
        if (lowerY < 0) {
            // move north
            lowerY = 0;
            upperY = 2*width;
         } else if (upperY >= size-1) {
             // move south
             lowerY = size-2*width-1;
             upperY = size-1;
         }
       
        List<List<Area>> matrix = new LinkedList<List<Area>>();
        for(int row = upperY; row>=lowerY; row--) {
            List<Area> rowArea = new LinkedList<Area>();
            matrix.add(rowArea);
            
            for(int column = lowerX; column<=upperX; column++) {
                Area area = getArea(new Location(column, row));
                if (area==null) {
                    throw new ArrayIndexOutOfBoundsException("Could not find area at location: " + new Location(column, row));
                }
                rowArea.add(area);
            } 
        }
        
        return matrix;
    }

    public void updateAreas(Area ... areas) {
    	for (final Area a : areas) {
    		Assert.isTrue(a.getPopulationCount()>=0, "Illegelal number of users in " + a);
    		boolean cityCreated = verifyCity(a.getId(), a.getCity());
    		getJpaTemplate().merge(a);
    		if (cityCreated) {
    			clientPublisher.publish(new CityCreated(a));
    		}
    			
    		if (a.getCity()==null){
    			getJpaTemplate().execute(new JpaCallback() {
					
					@Override
					public Object doInJpa(EntityManager em) throws PersistenceException {
						em.createQuery("delete com.nariano.server.domain.entities.City c where c.id="+a.getId()).executeUpdate();
						return null;
					}
				});
    		}
    	}
    }

    private boolean verifyCity(Integer areaId, City city) {
		if (city==null) { return false; }
		if (city.getId()==null) {
			city.setId(areaId);
			return true;
		} else {
			Assert.isTrue(areaId.equals(city.getId()));
			return false;
		}
	}
    
    public Area getArea(Integer id) {
    	return (Area) getJpaTemplate().find(Area.class, id);
	}

	public Area getArea(Location location) {
        List<Area> list = getJpaTemplate().find("select a from com.nariano.server.domain.entities.Area a where a.location.x="+location.getX()+" and a.location.y="+ location.getY());
        if (list.size()!=1) {
            return null;
        }
        return list.iterator().next();
    }

	public AreaBoundaries getBoundaries() {
		
		return new AreaBoundaries(size,size);
	}

	@Override
	public int getPopulationCount(final Integer user, final PopulationType type) { 
		// TODO replace with named query Population.countAndType
		Integer res = (Integer)getJpaTemplate().execute(new JpaCallback() {
			
			@Override
			public Object doInJpa(EntityManager em) throws PersistenceException {
				Query q = em.createQuery("select sum(p.number) from com.nariano.server.domain.entities.Population p where p.userId=:user and p.hibernateType=:type");
				
				q.setParameter("user", user);
				q.setParameter("type", type.getId());
				Object res = q.getSingleResult();
				return res == null ? 0 : ((Long)res).intValue();
			}
		});
			
		return res;
	}
	
	@Override
	public Population moveOutRandomPopulation(Integer user) {
		List<Population> res = getJpaTemplate().find("from com.nariano.server.domain.entities.Population p where p.userId = ? and p.number>0", user);
		if (res==null || res.isEmpty()) {
			return null;
		}
		Population pop = res.get(new Random().nextInt(res.size()));
		/*if (pop.getNumber()==1) {
			getHibernateTemplate().delete(pop);
		} else*/ 
		if (pop.getNumber()>0) {
			pop.setNumber(pop.getNumber()-1);
			getJpaTemplate().merge(pop);
			clientPublisher.publish(new PopulationChanged(null, user,pop.type(), -1));
			return new Population(user, pop.type(), 1);
		}
		return null;
	}

	@Override
	public List<Population> getPopulation(Integer user) {
		List<Population> res = getJpaTemplate().find("from com.nariano.server.domain.entities.Population p where p.userId = ? and p.number>0", user);
		if (res==null) {
			return Collections.EMPTY_LIST;
		}
		return res;
	}

       
}
